/******************************************************************************
 * This file is part of libemb.
 *
 * libemb is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * libemb is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with libemb.  If not, see <http://www.gnu.org/licenses/>.
 *
 * Project: Embedme
 * Author : FergusZeng
 * Email  : cblock@126.com
 * git	  : https://gitee.com/newgolo/embedme.git
 * Copyright 2014~2020 @ ShenZhen ,China
*******************************************************************************/
#include "Tracer.h"
#include "Gpio.h"
#include "FileUtil.h"
#include "StrUtil.h"

#include <utility>

#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

namespace libembx{
using namespace libemb;
/**
 *  @class  GpioGroup
 *  @brief  GPIO组结构体 
 */
class GpioGroup{
public:
	std::string m_label;/**< Gpio的组标 */
	int m_base;         /**< Gpio的基址 */
};

//GpioGroup的label和base信息可在/sys/class/gpio/gpiochipxx中查看,每种平台可能都不一样
#define GPIO_GROUP_MAX	7
static GpioGroup s_gpioGroups[GPIO_GROUP_MAX]={
    {"GPIO0",0},	/* GPIO0_n */
	{"GPIO1",32},	/* GPIO1_n */ 
	{"GPIO2",64},	/* GPIO2_n */
	{"GPIO3",96},	/* GPIO3_n */ 
	{"GPIO4",128},	/* GPIO4_n */
	{"GPIO5",160},	/* GPIO5_n */ 
    {"GPIO6",192},	/* GPIO6_n */
}; 

GpioImp::GpioImp(int id):
m_id(id)
{
	m_gpioDir="/sys/class/gpio/gpio";
	m_gpioDir += StrUtil::stringFormat("%d",m_id);
	m_directionFile = m_gpioDir+"/direction";
	m_valueFile = m_gpioDir+"/value";
	m_exportFile = "/sys/class/gpio/export";
}
GpioImp::~GpioImp()
{
}

/**
 *  @brief  配置GPIO
 *  @param  direction GPIO方向: GpioImp::IN或GpioImp::DIR_OUT
 *  @param  value GPIO值: GpioImp::LOW或GpioImp::HIGN
 *  @return 成功返回STATUS_OK,失败返回STATUS_ERROR
 *  @note   none
 */
int GpioImp::config(GpioDirection direction,GpioValue value)
{
	int i,rc,len,fd;
	
	/* 导出gpio */
	if(!Directory::exists(CSTR(m_gpioDir)))
	{
		fd = ::open(CSTR(m_exportFile),O_WRONLY);
		if(-1==fd)
		{
			TRACE_ERR_CLASS("open gpio(%d) export file error(%s).\n",m_id,ERRSTR);
			return RC_ERROR;
		}
		
		std::string buf = StrUtil::stringFormat("%d",m_id);
		len = buf.size();
		rc = ::write(fd, CSTR(buf), len);
		if (rc!=len)
		{
			TRACE_ERR_CLASS("write gpio(%d) export file failed,rc=%d,len=%d\n",m_id,rc,len);
			::close(fd);
			return RC_ERROR;
		}
		else
		{
			if(!Directory::exists(CSTR(m_gpioDir)))
			{
				TRACE_ERR_CLASS("cannot export gpio(%d)\n",m_id);
				::close(fd);
				return RC_ERROR;
			}
		}
		::close(fd);
	}

	i=0;
	while(1)
	{
		if (File::exists(CSTR(m_directionFile)))
		{
			break;
		}
		if(i>5)
		{
			return RC_ERROR;
		}
		Thread::msleep(1000);
	}

	/* 设置gpio属性 */
	fd = ::open(CSTR(m_directionFile),O_WRONLY);
	if(-1==fd)
	{
		TRACE_ERR_CLASS("open gpio(%d) direction file(%s) error(%s).\n",m_id,CSTR(m_directionFile),ERRSTR);
		return RC_ERROR;
	}
	if(direction==GpioDirection::Input)
	{
		len = 2;
		rc = ::write(fd, "in", len);
	}
	else
	{
		len =3;
		rc = ::write(fd, "out", len);
	}
	
	if (rc!=len)
	{
		TRACE_ERR_CLASS("gpio(%d)set direction error,rc=%d,len=%d\n",m_id,rc,len);
		::close(fd);
		return RC_ERROR;
	}
	::close(fd);

	if (direction==GpioDirection::Output)
	{
	    setValue(value);
	}
	return RC_OK;
}

/**
 *  @brief  设置GPIO Pin输出电平
 *  @param  value GPIO值: GpioImp::LOW或GpioImp::HIGN
 *  @return 成功返回true,失败返回false
 *  @note   none
 */
bool GpioImp::setValue(GpioValue value)
{
	int fd,rc;
	if(!Directory::exists(CSTR(m_gpioDir)))
	{
		TRACE_ERR_CLASS("gpio(%d) not config.\n",m_id);
		return false;
	}
	fd = ::open(CSTR(m_valueFile),O_RDWR);
	if(-1==fd)
	{
		TRACE_ERR_CLASS("open gpio(%d) value file(%s) error(%s).\n",m_id,CSTR(m_valueFile),ERRSTR);
		return false;
	}
	if(value==GpioValue::Low)
	{
		rc = ::write(fd, "0", 1);
	}
	else
	{
		rc = ::write(fd, "1", 1);
	}
	if (rc!=1)
	{
		TRACE_ERR_CLASS("gpio(%d) set value error(%s),rc=%d\n",m_id,ERRSTR,rc);
		::close(fd);
		return false;
	}
	::close(fd);
	return true;
}

/**
 *  @brief  读取GPIO Pin输入电平
 *  @param  void
 *  @return 成功返回pin值,失败返回STATUS_ERROR
 *  @note   none
 */
GpioValue GpioImp::getValue()
{
	int fd,rc;
	if(!Directory::exists(CSTR(m_gpioDir)))
	{
		TRACE_ERR_CLASS("gpio(%d) not config.\n",m_id);
		return GpioValue::Unsure;
	}
	
	fd = ::open(CSTR(m_valueFile),O_RDWR);
	if(-1==fd)
	{
		TRACE_ERR_CLASS("open gpio(%d) direction file(%s) error(%s).\n",m_id,CSTR(m_valueFile),ERRSTR);
		return GpioValue::Unsure;
	}
	char value;
	rc = ::read(fd, &value, 1);
	if (rc!=1)
	{
		TRACE_ERR_CLASS("gpio(%d) get value error(%s),rc=%d\n",m_id,ERRSTR,rc);
		::close(fd);
		return GpioValue::Unsure;
	}
	::close(fd);
	if(value=='0')
	{
		return GpioValue::Low;	
	}
	else
	{
		return GpioValue::High;
	}
}
/**
 *  @brief  GPIO工厂获取GPIO实例
 *  @param  groupName GPIO名称字符串
 *  @return 成功返回GPIO实例GpioImp*,失败返回NULL
 *  @note   groupName格式:"group:pin",如"GPIO0:2"表示第0组,第2引脚.
 */
std::shared_ptr<GpioImp> GpioFactory::getGpioImp(std::string gpioName)
{
	std::string group,pin;
	int pos = gpioName.find(":");
	if(pos==std::string::npos)
	{
		TRACE_ERR_CLASS("%s not exist!\n",CSTR(gpioName));
		return nullptr;
	}
	group = gpioName.substr(0,pos);
	pin = gpioName.substr(pos+1);
	if (group.empty()|| pin.empty())
	{
		TRACE_ERR_CLASS("%s not exist!\n",CSTR(gpioName));
		return nullptr;
	}
    return getGpioImp(group,StrUtil::stringToInt(pin));
}
/**
 *  @brief  GPIO工厂获取GPIO实例
 *  @param  group GPIO所在组
 *  @param  pin GPIO pin脚
 *  @return 成功返回GPIO实例GpioImp*,失败返回NULL
 *  @note   none
 */
std::shared_ptr<GpioImp> GpioFactory::getGpioImp(std::string group, int pin)
{
	int gpioNum=-1;
	for(auto i=0; i<GPIO_GROUP_MAX; i++)
	{
		if(group==s_gpioGroups[i].m_label)
		{
			gpioNum = s_gpioGroups[i].m_base+pin;
			break;
		}
	}
	if(-1==gpioNum)
	{
		TRACE_ERR_CLASS("No GPIO(%s:%d)!\n",CSTR(group),pin);
		return nullptr;
	}
	auto iter=m_gpioImpTable.find(gpioNum);
	if(iter!=m_gpioImpTable.end())
	{
		return iter->second;
	}
	auto gpioImp = std::make_shared<GpioImp>(gpioNum);
	m_gpioImpTable.insert(std::make_pair(gpioNum, gpioImp));
	return gpioImp;
}

void GpioFactory::clear()
{
	m_gpioImpTable.clear();
}

}
